package ling.learn.antlr;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Stack;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.io.FileUtils;

/**
 * 下記の簡単な文法が対応できる.
 * <p>
 * fdStmt.csv
 * <p>
 * moveStmt.csv
 * <p>
 * 複雑文法、ループ文法は未対応
 **/
public class Syntax2G4Converter {
  private Map<String, String> operatorMap = new HashMap<>();
  private Map<String, String> operatorUsedMap = new HashMap<>();
  private Map<String, String> keywordUsedMap = new HashMap<>();
  private Map<String, String> otherUsedMap = new HashMap<>();
  private Map<String, String> ruleMap = new HashMap<>();
  private int ruleIdMaxLen = 23;
  private String ruleIdSpaces = "";
  private Stack<String> statusStack = new Stack<>();
  private Stack<String> ruleStack = new Stack<>();

  public Syntax2G4Converter() {
    String rulePrefix = "OP_";
    operatorMap.put(":=", rulePrefix + "ASSIGN");
    operatorMap.put("<>", rulePrefix + "NOT_EQUAL1");
    operatorMap.put("!=", rulePrefix + "NOT_EQUAL2");
    operatorMap.put("<=", rulePrefix + "LE");
    operatorMap.put(">=", rulePrefix + "GE");
    operatorMap.put("(.", rulePrefix + "LBRACK2");
    operatorMap.put(".)", rulePrefix + "RBRACK2");

    operatorMap.put("+", rulePrefix + "PLUS");
    operatorMap.put("-", rulePrefix + "MINUS");
    operatorMap.put("*", rulePrefix + "STAR");
    operatorMap.put("/", rulePrefix + "SLASH");
    operatorMap.put(",", rulePrefix + "COMMA");
    operatorMap.put(";", rulePrefix + "SEMI");
    operatorMap.put(":", rulePrefix + "COLON");
    operatorMap.put("=", rulePrefix + "EQUAL");
    operatorMap.put("<", rulePrefix + "LT");
    operatorMap.put(">", rulePrefix + "GT");
    operatorMap.put("(", rulePrefix + "LPAREN");
    operatorMap.put(")", rulePrefix + "RPAREN");
    operatorMap.put("[", rulePrefix + "LBRACK");
    operatorMap.put("]", rulePrefix + "RBRACK");
    operatorMap.put("{", rulePrefix + "LCURLY");
    operatorMap.put("}", rulePrefix + "RCURLY");
    operatorMap.put("\\", rulePrefix + "YEN");
    operatorMap.put(".", rulePrefix + "DOT");
    operatorMap.put("$", rulePrefix + "DOLLAR");
    operatorMap.put("|", rulePrefix + "OR");
    operatorMap.put("&", rulePrefix + "AMBER");
    operatorMap.put("~", rulePrefix + "NOT");
    operatorMap.put("?", rulePrefix + "HELP");

    ruleIdSpaces = String.join("", Collections.nCopies(30, " "));
  }

  public static void execute(String grammarName) throws IOException {
    String dirPath = "resource\\" + grammarName + "_Syntax\\";
    String template =
        FileUtils.readFileToString(new File(dirPath + "template.g4"), Charset.forName("UTF-8"));
    String text = template.replace("#grammarName", grammarName);
    Collection<File> files = FileUtils.listFiles(new File(dirPath), new String[] {"csv"}, true);
    StringBuilder stream = new StringBuilder();
    Syntax2G4Converter genSyntax = new Syntax2G4Converter();
    for (File file : files) {
      List<String> lines = FileUtils.readLines(file, Charset.forName("UTF-8"));
      String ruleId = file.getName().substring(0, file.getName().length() - 4);
      genSyntax.genRule(ruleId, stream, lines);
    }
    text = text.replace("#ruleList", stream.toString());

    stream = new StringBuilder();
    genSyntax.genRuleLine(stream);
    text = text.replace("#ruleLine", stream.toString());

    stream = new StringBuilder();
    genSyntax.genOtherRule(stream);
    text = text.replace("#otherRuleList", stream.toString());

    stream = new StringBuilder();
    genSyntax.genKeywordRule(stream);
    text = text.replace("#keyword", stream.toString());

    stream = new StringBuilder();
    genSyntax.genOperatorRule(stream);
    text = text.replace("#operator", stream.toString());

    FileUtils.write(new File(dirPath + grammarName + ".g4"), text, Charset.forName("UTF-8"));
  }

  public void genKeywordRule(StringBuilder stream) {
    joinRule("keyword", stream, keywordUsedMap);
  }

  public void genOperatorRule(StringBuilder stream) {
    for (Map.Entry<String, String> entry : operatorMap.entrySet()) {
      operatorUsedMap.put(entry.getValue(), entry.getKey());
    }
    joinRule("operator", stream, operatorUsedMap);
  }

  public void genOtherRule(StringBuilder stream) {
    joinRule("", stream, otherUsedMap);
  }

  public void genRuleLine(StringBuilder stream) {
    joinRule("ruleLine", stream, ruleMap);
  }

  private String getRuleId(String oldRuleId) {
    String newRuleId = "";
    int ruleIdLen = oldRuleId.length();
    byte[] bytes;
    try {
      bytes = oldRuleId.getBytes("MS932");
      ruleIdLen = bytes.length;
    } catch (UnsupportedEncodingException e) {
      e.printStackTrace();
    }
    if (ruleIdLen < ruleIdMaxLen) {
      newRuleId = oldRuleId + ruleIdSpaces;
      newRuleId = newRuleId.substring(0, ruleIdMaxLen - ruleIdLen + oldRuleId.length());
    } else {
      newRuleId = oldRuleId;
    }
    return newRuleId;
  }

  private void joinRule(final String ruleId, StringBuilder stream,
      final Map<String, String> ruleMap) {
    if (ruleMap.isEmpty()) {
      return;
    }

    String ruleList = "";
    String ruleText = "";
    String ruleIdText = "";

    Map<String, String> treeMap = new TreeMap<>(new Comparator<String>() {

      @Override
      public int compare(String o1, String o2) {
        return ruleMap.getOrDefault(o1, "").compareTo(ruleMap.getOrDefault(o2, ""));
        // if (ruleId.isEmpty()) {
        // return o1.compareTo(o2);
        // }
        // if (o1.length() > o2.length()) {
        // return -1;
        // } else if (o1.length() < o2.length()) {
        // return 1;
        // } else {
        // return o1.compareTo(o2);
        // }
      }
    });

    treeMap.putAll(ruleMap);
    String spaces = "";
    if (!ruleId.isEmpty()) {
      ruleIdText = getRuleId(ruleId);
      spaces = String.join("", Collections.nCopies(ruleIdText.length(), " "));
    }
    for (Map.Entry<String, String> entry : treeMap.entrySet()) {
      String text = "";
      String id = entry.getKey();
      if (id.startsWith("KW_")) {
        text = "'" + entry.getValue() + "'";
      } else if (id.startsWith("OP_")) {
        text = "'" + entry.getValue().replace("\\", "\\\\") + "'";
      } else if (id.startsWith("ws_")) {
        text = "wordList";
      } else if (id.startsWith("wd_")) {
        text = "word";
      } else if (id.startsWith("no_")) {
        text = "numericLiteral";
      } else if (id.startsWith("st_")) {
        text = "nonnumericLiteral";
      } else if (id.startsWith("id_")) {
        text = "Identifier";
      }
      if (!text.isEmpty()) {
        ruleList += getRuleId(id) + " : " + text + " ;\r\n";
      }
      if (!ruleId.isEmpty()) {
        if (!ruleText.isEmpty()) {
          ruleText += spaces + " | " + id + "\r\n";
        } else {
          ruleText += id + "\r\n";
        }
      }
    }
    if (!ruleId.isEmpty()) {
      stream.append(
          "//----------------------------------------------------------------------------\r\n");
      stream.append("// ");
      stream.append(ruleId.toUpperCase());
      stream.append("\r\n");
      stream.append(
          "//----------------------------------------------------------------------------\r\n");
      stream.append(ruleIdText);
      stream.append(" : ");
      stream.append(ruleText);
      stream.append(spaces + " ;\r\n");
    }
    stream.append(ruleList);
  }

  public void genRule(String ruleId, StringBuilder stream, List<String> lines) {
    statusStack.clear();
    ruleStack.clear();

    Map<String, Map<String, List<String>>> statusMap = new ConcurrentHashMap<>();
    for (String line : lines) {
      if (line.startsWith("ID")) {
        continue;
      }
      String[] words = line.split("\t");
      int i = 0;
      if (words.length >= 11) {
        String statusNo = words[++i] + words[++i];
        String keyWordParm = words[++i];
        String nextNo = words[++i] + words[++i];
        String language = words[++i];
        String version = words[++i];
        String keyKBN = words[++i];
        String rulePrefix = "";
        switch (keyKBN) {
          case "0":
            rulePrefix = "KW_";
            break;
          case "1":
            rulePrefix = "OP_";
            break;
          case "2":
            if (!keyWordParm.isEmpty()) {
              rulePrefix = "ws_";
              keyWordParm = keyWordParm.toLowerCase();
            }
            break;
          case "6":
            rulePrefix = "wd_";
            keyWordParm = keyWordParm.toLowerCase();
            break;
          case "14":
            rulePrefix = "no_";
            keyWordParm = keyWordParm.toLowerCase();
            break;
          case "22":
            rulePrefix = "st_";
            keyWordParm = keyWordParm.toLowerCase();
            break;
          case "54":
            rulePrefix = "id_";
            keyWordParm = keyWordParm.toLowerCase();
            break;
          default:
            break;
        }
        Map<String, List<String>> nextStatusMap =
            statusMap.getOrDefault(statusNo, new HashMap<String, List<String>>());
        List<String> ruleList = nextStatusMap.getOrDefault(nextNo, new ArrayList<String>());
        if (keyWordParm.isEmpty()) {
          ruleList.add("");
          nextStatusMap.put(nextNo, ruleList);
        } else {
          String rule = "";
          String[] keyWordParms = keyWordParm.split("\\$");
          for (String parm : keyWordParms) {
            String subRule = "";
            String[] ruleNames = parm.split(" ");
            for (String ruleName : ruleNames) {
              if (keyKBN.equals("1")) {
                if (operatorMap.containsKey(ruleName)) {
                  subRule += " " + operatorMap.get(ruleName);
                  // operatorUsedMap.put(ruleName, operatorMap.get(ruleName));
                } else {
                  subRule += " " + rulePrefix + ruleName;
                  operatorUsedMap.put(rulePrefix + ruleName, ruleName);
                }
              } else if (keyKBN.equals("0")) {
                subRule += " " + rulePrefix + ruleName;
                keywordUsedMap.put(rulePrefix + ruleName, ruleName);
              } else {
                subRule += " " + rulePrefix + ruleName;
                otherUsedMap.put(rulePrefix + ruleName, ruleName);
              }
            }
            subRule = subRule.trim();
            if (ruleNames.length > 1) {
              subRule = "( " + subRule + " )";
            }
            if (!rule.isEmpty()) {
              rule += " | ";
            }
            rule += subRule;
          }
          if (keyWordParms.length > 1) {
            rule = "( " + rule + " )";
          }
          if (nextNo.equals(statusNo)) {
            rule += "?";
          }
          ruleList.add(rule);
          nextStatusMap.put(nextNo, ruleList);
        }
        statusMap.put(statusNo, nextStatusMap);
      }
    }
    String ruleText = genRuleText("000000", statusMap);
    ruleMap.put(ruleId, ruleId);
    stream.append(getRuleId(ruleId));
    stream.append(" : ");
    stream.append(ruleText);
    stream.append("  (OP_DOT)? EOL ;\r\n");
  }

  private String genRuleText(String statusNo, Map<String, Map<String, List<String>>> statusMap) {

    statusStack.push(statusNo);

    String ruleText = "";
    Map<String, List<String>> nextStatusMap =
        statusMap.getOrDefault(statusNo, new HashMap<String, List<String>>());
    int selectCount = 0;
    int loopCount = 0;
    String selectRule = "";
    for (Entry<String, List<String>> entry : nextStatusMap.entrySet()) {
      String nextNo = entry.getKey();
      if (!nextNo.equals(statusNo) && statusStack.contains(nextNo)) {
        loopCount++;
        // ruleText += "+";
        continue;
      }
      List<String> subRules = entry.getValue();
      String subRuleText = "";
      boolean isSubRuleSelect = false;
      int subRuleSelect = 0;
      for (String subRule : subRules) {
        if (subRule.isEmpty()) {
          isSubRuleSelect = true;
          subRuleSelect++;
        } else {
          if (!subRuleText.isEmpty()) {
            subRuleText += " | ";
          }
          subRuleText += subRule;
        }
      }
      if (subRules.size() - subRuleSelect > 1) {
        subRuleText = "( " + subRuleText + " )";
      }
      if (isSubRuleSelect == true) {
        subRuleText += "?";
      }
      if (nextNo.equals(statusNo)) {
        selectCount++;
        if (!selectRule.isEmpty()) {
          selectRule += " ";
        }
        selectRule += subRuleText;
        continue;
      } else if (!nextNo.endsWith("END")) {
        if (subRuleText.isEmpty()) {
          subRuleText = genRuleText(nextNo, statusMap);
        } else {
          subRuleText += " ";

          subRuleText = subRuleText + genRuleText(nextNo, statusMap);
        }
      }
      if (!ruleText.isEmpty()) {
        ruleText += " | ";
      }
      ruleText += subRuleText;
    }
    if (nextStatusMap.size() - selectCount - loopCount > 1) {
      ruleText = "( " + ruleText + " )";
    }
    if (!selectRule.isEmpty()) {
      ruleText = selectRule + " " + ruleText;
    }
    statusStack.pop();
    return ruleText;
  }
}
